/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.tools;

import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.EditPartListener;
import com.architexa.org.eclipse.gef.EditPartViewer;
import com.architexa.org.eclipse.gef.Request;
import com.architexa.org.eclipse.gef.RequestConstants;
import com.architexa.org.eclipse.gef.SharedCursors;
import com.architexa.org.eclipse.gef.commands.Command;
import com.architexa.org.eclipse.gef.requests.CreateConnectionRequest;
import com.architexa.org.eclipse.gef.requests.CreateRequest;
import com.architexa.org.eclipse.gef.requests.CreationFactory;

import org.eclipse.swt.graphics.Cursor;


/**
 * The base implementation for tools which create a connection.  A connection is a link
 * between two existing GraphicalEditParts.
 * <P>
 * A connection creation tool uses a {@link CreateConnectionRequest} to perform the
 * creation.  This request is sent to both graphical editparts which serve as the "nodes"
 * at each end of the connection.  The first node clicked on is the source.  The source is
 * asked for a <code>Command</code> that represents creating the first half of the
 * connection.  This command is then passed to the target editpart, which is reponsible
 * for creating the final Command that is executed.
 * @author hudsonr
 */
public class AbstractConnectionCreationTool
	extends TargetingTool
{

/**
 * The state which indicates that the connection creation has begun.  This means that the
 * source of the connection has been identified, and the user is still to determine the
 * target.
 */
protected static final int STATE_CONNECTION_STARTED = TargetingTool.MAX_STATE << 1;
/**
 * The max state.
 */
protected static final int MAX_STATE = STATE_CONNECTION_STARTED;

private static final int FLAG_SOURCE_FEEDBACK = TargetingTool.MAX_FLAG << 1;

/**
 * The max flag.
 */
protected static final int MAX_FLAG = FLAG_SOURCE_FEEDBACK;

private EditPart connectionSource;
private CreationFactory factory;
private EditPartViewer viewer;

private EditPartListener.Stub deactivationListener = new EditPartListener.Stub() {
	public void partDeactivated(EditPart editpart) {
		handleSourceDeactivated();
	}
};

/**
 * The default constructor
 */
public AbstractConnectionCreationTool() {
	setDefaultCursor(SharedCursors.CURSOR_PLUG);
	setDisabledCursor(SharedCursors.CURSOR_PLUG_NOT);
}

/**
 * Constructs a new abstract creation tool with the given creation factory.
 * @param factory the creation factory
 */
public AbstractConnectionCreationTool(CreationFactory factory) {
	this();
	setFactory(factory);
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#calculateCursor()
 */
protected Cursor calculateCursor() {
	if (isInState(STATE_INITIAL)) {
		if (getCurrentCommand() != null)
			return getDefaultCursor();
	}
	return super.calculateCursor();
}

/**
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#createTargetRequest()
 */
protected Request createTargetRequest() {
	CreateRequest req = new CreateConnectionRequest();
	req.setFactory(getFactory());
	return req;
}

/**
 * Erases feedback and sets fields to <code>null</code>.
 * @see com.architexa.org.eclipse.gef.Tool#deactivate()
 */
public void deactivate() {
	eraseSourceFeedback();
	setConnectionSource(null);
	super.deactivate();
	setState(STATE_TERMINAL);
	viewer = null;
}

/**
 * Asks the source editpart to erase connection creation feedback.
 */
protected void eraseSourceFeedback() {
	if (!isShowingSourceFeedback())
		return;
	setFlag(FLAG_SOURCE_FEEDBACK, false);
	if (connectionSource != null) {
		connectionSource.eraseSourceFeedback(getSourceRequest());
	}
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getCommandName()
 */
protected String getCommandName() {
	if (isInState(STATE_CONNECTION_STARTED | STATE_ACCESSIBLE_DRAG_IN_PROGRESS))
		return REQ_CONNECTION_END;
	else
		return REQ_CONNECTION_START;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getDebugName()
 */
protected String getDebugName() {
	return "Connection Creation Tool";//$NON-NLS-1$
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getDebugNameForState(int)
 */
protected String getDebugNameForState(int s) {
	if (s == STATE_CONNECTION_STARTED || s == STATE_ACCESSIBLE_DRAG_IN_PROGRESS)
		return "Connection Started";//$NON-NLS-1$
	return super.getDebugNameForState(s);
}

/**
 * Returns the creation factory that will be used with the create connection request.
 * @return the creation factory
 */
protected CreationFactory getFactory() {
	return factory;
}

/**
 * Returns the request sent to the source node.  The source node receives the same request
 * that is used with the target node.  The only difference is that at that time the
 * request will be typed as {@link RequestConstants#REQ_CONNECTION_START}.
 * @return the request used with the source node editpart
 */
protected Request getSourceRequest() {
	return getTargetRequest();
}

/**
 * When the button is first pressed, the source node and its command contribution are
 * determined and locked in.  After that time, the tool will be looking for the target
 * node to complete the connection
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleButtonDown(int)
 * @param button which button is pressed
 * @return <code>true</code> if the button down was processed
 */
protected boolean handleButtonDown(int button) {
	if (isInState(STATE_INITIAL) && button == 1) {
		updateTargetRequest();
		updateTargetUnderMouse();
		setConnectionSource(getTargetEditPart());
		Command command = getCommand();
		((CreateConnectionRequest)getTargetRequest()).setSourceEditPart(
			getTargetEditPart());
		if (command != null) {
			setState(STATE_CONNECTION_STARTED);
			setCurrentCommand(command);
			viewer = getCurrentViewer();
		}
	}

	if (isInState(STATE_INITIAL) && button != 1) {
		setState(STATE_INVALID);
		handleInvalidInput();
	}
	return true;
}

/**
 * Unloads or resets the tool if the state is in the terminal or invalid state.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
 */
protected boolean handleButtonUp(int button) {
	if (isInState(STATE_TERMINAL | STATE_INVALID))
		handleFinished();
	return true;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleCommandStackChanged()
 */
protected boolean handleCommandStackChanged() {
	if (!isInState(STATE_INITIAL)) {
		if (getCurrentInput().isMouseButtonDown(1))
			setState(STATE_INVALID);
		else
			setState(STATE_INITIAL);	
		handleInvalidInput();
		return true;
	}
	return false;
}

/**
 * Method that is called when the gesture to create the connection has been received.
 * Subclasses may extend or override this method to do additional creation setup, such as
 * prompting the user to choose an option about the connection being created. Returns
 * <code>true</code> to indicate that the connection creation succeeded.
 * @return <code>true</code> if the connection creation was performed
 */
protected boolean handleCreateConnection() {
	eraseSourceFeedback();
	Command endCommand = getCommand();
	setCurrentCommand(endCommand);
	executeCurrentCommand();
	return true;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleDrag()
 */
protected boolean handleDrag() {
	if (isInState(STATE_CONNECTION_STARTED))
		return handleMove();
	return false;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
 */
protected boolean handleDragInProgress() {
	if (isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS))
		return handleMove();
	return false;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleFocusLost()
 */
protected boolean handleFocusLost() {
	if (isInState(STATE_CONNECTION_STARTED)) {
		eraseSourceFeedback();
		eraseTargetFeedback();
		setState(STATE_INVALID);
		handleFinished();
	}
	return super.handleFocusLost();
}

/**
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#handleHover()
 */
protected boolean handleHover() {
	if (isInState(STATE_CONNECTION_STARTED))
		updateAutoexposeHelper();
	return true;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#handleInvalidInput()
 */
protected boolean handleInvalidInput() {
	eraseSourceFeedback();
	setConnectionSource(null);
	return super.handleInvalidInput();
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleMove()
 */
protected boolean handleMove() {
	if (isInState(STATE_CONNECTION_STARTED) && viewer != getCurrentViewer())
		return false;
	if (isInState(STATE_CONNECTION_STARTED | STATE_INITIAL 
			| STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
		updateTargetRequest();
		updateTargetUnderMouse();
		showSourceFeedback();
		showTargetFeedback();
		setCurrentCommand(getCommand());
	}
	return true;
}

/**
 * Called if the source editpart is deactivated for some reason during the creation
 * process.  For example, the user performs an Undo while in the middle of creating a
 * connection, which undoes a prior command which created the source.
 */
protected void handleSourceDeactivated() {
	setState(STATE_INVALID);
	handleInvalidInput();
	handleFinished();
}

/**
 * Returns <code>true</code> if feedback is being shown.
 * @return <code>true</code> if showing source feedback
 */
protected boolean isShowingSourceFeedback() {
	return getFlag(FLAG_SOURCE_FEEDBACK);
}

/**
 * Sets the source editpart for the creation
 * @param source the source editpart node
 */
protected void setConnectionSource(EditPart source) {
	if (connectionSource != null)
		connectionSource.removeEditPartListener(deactivationListener);
	connectionSource = source;
	if (connectionSource != null)
		connectionSource.addEditPartListener(deactivationListener);
}

/**
 * Sets the creation factory used in the request.
 * @param factory the factory
 */
public void setFactory(CreationFactory factory) {
	this.factory = factory;
}

/**
 * Sends a show feedback request to the source editpart and sets the feedback flag.
 */
protected void showSourceFeedback() {
	if (connectionSource != null) {
		connectionSource.showSourceFeedback(getSourceRequest());
	}
	setFlag(FLAG_SOURCE_FEEDBACK, true);
}

/**
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#updateTargetRequest()
 */
protected void updateTargetRequest() {
 	CreateConnectionRequest request = (CreateConnectionRequest)getTargetRequest();
	request.setType(getCommandName());
	request.setLocation(getLocation());
}

}
